// --------------------------------------------------------------------------------------------------------------------
// <copyright file="VirtualMarket.Matching.Limit.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge.Plugins
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// The virtual market.
    /// </summary>
    public partial class VirtualMarket
    {
        #region Methods

        /// <summary>
        /// The generate and insert maket book update.
        /// </summary>
        /// <param name="quantityToRemove">
        /// The quantityToRemove.
        /// </param>
        /// <param name="destinationMarketBook">
        /// The destination_ market book.
        /// </param>
        /// <param name="originalCell">
        /// The originalCell.
        /// </param>
        /// <param name="originalCellSide">
        /// The originalCellSide.
        /// </param>
        /// <param name="originalCellDepth">
        /// The originalCellDepth.
        /// </param>
        private static void GenerateAndInsertMaketBookUpdate(int quantityToRemove, 
                                                             IMarketBook destinationMarketBook, 
                                                             MarketBookCell originalCell, 
                                                             ESide originalCellSide, 
                                                             short originalCellDepth)
        {
            // Here we update contributions, sequentially supressing them if completely grabed !
            var toRemove = quantityToRemove;
            IList<IContribution> contributions = new List<IContribution>();
            foreach (var c in originalCell.Contributions)
            {
                var adjustedContribution = new Contribution(c.ID, "Virtual Contributor", Math.Max(0, toRemove - c.Size));
                toRemove = Math.Max(0, toRemove - c.Size);
                if (adjustedContribution.Size > 0)
                {
                    contributions.Add(c);
                }
            }

            switch (originalCellSide)
            {
                case ESide.Bid:
                    var bidcell = MarketBookCell.Bid(
                        originalCellDepth, Timings.Now, originalCell.Price, originalCell.Size - quantityToRemove, contributions.ToArray());
                    destinationMarketBook.HandleUpdate(new FeedEventArgs.MarketBookUpdate(Guid.Empty, new[] { bidcell }, false));
                    break;

                case ESide.Ask:
                    var askcell = MarketBookCell.Ask(
                        originalCellDepth, Timings.Now, originalCell.Price, originalCell.Size - quantityToRemove, contributions.ToArray());
                    destinationMarketBook.HandleUpdate(new FeedEventArgs.MarketBookUpdate(Guid.Empty, new[] { askcell }, false));
                    break;
            }
        }

        /// <summary>
        /// Try to much the order
        /// </summary>
        /// <param name="order">
        /// The virtuak market order
        /// </param>
        private void CheckImmediateExecution(VirtualMarketOrder order)
        {
            if (order == null)
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CheckImmediateExecution : Unknown order"));
                return;
            }

            if (order.Pending || order.Cancelling || order.Modifying)
            {
                return;
            }

            switch (order.Way)
            {
                case EOrderWay.Buy:
                    {
                        var qty = 0;
                        var price = 0.0;

                        // Check Market Book
                        if (this.MarketBook != null)
                        {
                            if (this.MarketBook.Asks != null)
                            {
                                if (this.MarketBook.Asks[0].HasValue)
                                {
                                    qty = this.MarketBook.Asks[0].Size;
                                    price = this.MarketBook.Asks[0].Price;
                                }
                            }
                        }

                        // Check Qty & Price
                        if (qty > 0 && price > Constants.Zero)
                        {
                            if (order.Price >= price)
                            {
                                var quotefactor = (double)this.OrderPlugin.Configuration["QuoteFactor"];
                                var availableqty = Math.Min(order.Quantity, (int)(quotefactor * qty));
                                var remainingqty = order.Quantity - availableqty;
                                order.Quantity = remainingqty;

                                if (availableqty > 0)
                                {
                                    // Update MarketBook
                                    GenerateAndInsertMaketBookUpdate(availableqty, this.MarketBook, this.MarketBook.Asks[0], ESide.Ask, 0);

                                    // Execute
                                    this.OrderPlugin.RaiseEventOrderExecution(order.OrderID, order.Way, price, availableqty, remainingqty, order.Dispatcher);

                                    // If Completely filled
                                    if (remainingqty == 0)
                                    {
                                        // Remove Order
                                        this.OrderCellsQueue.Remove(order);

                                        // Order is now done
                                        this.OrderPlugin.RaiseEventOutReport(order.OrderID, order.Way, order.Dispatcher);
                                    }
                                }
                            }
                        }
                    }

                    break;
                case EOrderWay.Sell:
                    {
                        var qty = 0;
                        var price = 0.0;

                        // Check Market Book
                        if (this.MarketBook != null)
                        {
                            if (this.MarketBook.Bids != null)
                            {
                                if (this.MarketBook.Bids[0].HasValue)
                                {
                                    qty = this.MarketBook.Bids[0].Size;
                                    price = this.MarketBook.Bids[0].Price;
                                }
                            }
                        }

                        // Check Qty & Price
                        if (qty > 0 && price > Constants.Zero)
                        {
                            if (order.Price <= price)
                            {
                                var quotefactor = (double)this.OrderPlugin.Configuration["QuoteFactor"];
                                var availableqty = Math.Min(order.Quantity, (int)(quotefactor * qty));
                                var remainingqty = order.Quantity - availableqty;
                                order.Quantity = remainingqty;

                                if (availableqty > 0)
                                {
                                    // Update MarketBook
                                    GenerateAndInsertMaketBookUpdate(availableqty, this.MarketBook, this.MarketBook.Bids[0], ESide.Bid, 0);

                                    // Execute
                                    this.OrderPlugin.RaiseEventOrderExecution(order.OrderID, order.Way, price, availableqty, remainingqty, order.Dispatcher);

                                    // If Completely filled
                                    if (remainingqty == 0)
                                    {
                                        // Remove Order
                                        this.OrderCellsQueue.Remove(order);

                                        // Order is now done
                                        this.OrderPlugin.RaiseEventOutReport(order.OrderID, order.Way, order.Dispatcher);
                                    }
                                }
                            }
                        }
                    }

                    break;
            }
        }

        /// <summary>
        /// The enqueue limit order for asynchronous matching.
        /// </summary>
        /// <param name="order">
        /// The order.
        /// </param>
        /// <param name="handler">
        /// The handler.
        /// </param>
        private void EnqueueLimitOrderForAsynchronousMatching(IOrder order, RemotableEventHandler<OrderEventArgs> handler)
        {
            lock (this.OrderCellsQueue)
            {
                // Enqueue this order
                this.OrderCellsQueue.AddRange(new VirtualMarketOrder(order, handler));
            }
        }

        /// <summary>
        /// Find orders written on instrumentID if any and match them, from oldest to newest
        /// </summary>
        private void LimitOrdersTryMatch()
        {
            lock (this.OrderCellsQueue)
            {
                // Find orders written on this instrument (if any)
                var orders = this.OrderCellsQueue.ToArray();

                // If such orders exists, match them if possible
                foreach (var order in orders)
                {
                    if (!order.Pending && !order.Cancelling && !order.Modifying)
                    {
                        if (this.CanExecute())
                        {
                            // Buy
                            switch (order.Way)
                            {
                                case EOrderWay.Buy:
                                    {
                                        var qty = this.LastSize.HasValue ? this.LastSize : 0;
                                        var price = this.Last.HasValue ? this.Last : 0.0;

                                        if (qty > 0 && price > Constants.Zero)
                                        {
                                            if (order.Price >= price)
                                            {
                                                var quotefactor = (double)this.OrderPlugin.Configuration["QuoteFactor"];
                                                var availableqty = Math.Min(order.Quantity, (int)(quotefactor * qty));
                                                var remainingqty = order.Quantity - availableqty;
                                                order.Quantity = remainingqty;

                                                if (availableqty > 0)
                                                {
                                                    var competition = (bool)this.OrderPlugin.Configuration["Competition"];

                                                    // Update MarketBook
                                                    if (competition)
                                                    {
                                                        GenerateAndInsertMaketBookUpdate(availableqty, this.MarketBook, this.MarketBook.Asks[0], ESide.Ask, 0);
                                                    }
                                                    else
                                                    {
                                                        this.LastSize = new MarketField(EMarketField.LastSize, qty - availableqty);
                                                    }

                                                    // Execute
                                                    this.OrderPlugin.RaiseEventOrderExecution(
                                                        order.OrderID, order.Way, price, availableqty, remainingqty, order.Dispatcher);

                                                    // If Completely filled
                                                    if (remainingqty == 0)
                                                    {
                                                        // Remove Order
                                                        this.OrderCellsQueue.Remove(order);

                                                        // Order is now done
                                                        this.OrderPlugin.RaiseEventOutReport(order.OrderID, order.Way, order.Dispatcher);
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    break;
                                case EOrderWay.Sell:
                                    {
                                        var qty = this.LastSize.HasValue ? this.LastSize : 0;
                                        var price = this.Last.HasValue ? this.Last : 0.0;

                                        if (qty > 0 && price > Constants.Zero)
                                        {
                                            if (order.Price <= price)
                                            {
                                                var quotefactor = (double)this.OrderPlugin.Configuration["QuoteFactor"];
                                                var availableqty = Math.Min(order.Quantity, (int)(quotefactor * qty));
                                                var remainingqty = order.Quantity - availableqty;
                                                order.Quantity = remainingqty;

                                                if (availableqty > 0)
                                                {
                                                    var competition = (bool)this.OrderPlugin.Configuration["Competition"];

                                                    // Update MarketBook
                                                    if (competition)
                                                    {
                                                        GenerateAndInsertMaketBookUpdate(availableqty, this.MarketBook, this.MarketBook.Bids[0], ESide.Bid, 0);
                                                    }
                                                    else
                                                    {
                                                        this.LastSize = new MarketField(EMarketField.LastSize, qty - availableqty);
                                                    }

                                                    // Execute
                                                    this.OrderPlugin.RaiseEventOrderExecution(
                                                        order.OrderID, order.Way, price, availableqty, remainingqty, order.Dispatcher);

                                                    // If Completely filled
                                                    if (remainingqty == 0)
                                                    {
                                                        // Remove Order
                                                        this.OrderCellsQueue.Remove(order);

                                                        // Order is now done
                                                        this.OrderPlugin.RaiseEventOutReport(order.OrderID, order.Way, order.Dispatcher);
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    break;
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Update Pending orders (new, cancelling or modifying)
        /// </summary>
        private void LimitOrdersUpdatePending()
        {
            lock (this.OrderCellsQueue)
            {
                // Find orders written on this instrument (if any)
                var orders = this.OrderCellsQueue.ToArray();
                var ackdelay = (int)this.OrderPlugin.Configuration["AckDelay"];
                foreach (var o in orders)
                {
                    // Pending Order
                    if (o.Pending)
                    {
                        var delay = Timings.Now - o.Stamp;
                        if (delay.TotalMilliseconds > ackdelay)
                        {
                            // unset pending order
                            o.Pending = false;

                            // Raise Ack Order
                            this.OrderPlugin.RaiseEventAckOrder(o.OrderID, o.Way, o.Quantity, o.Quantity, o.Price, o.Dispatcher);

                            // Check immediate execution
                            this.CheckImmediateExecution(o);
                        }
                    }
                    else if (o.Cancelling)
                    {
                        var delay = Timings.Now - o.Stamp;
                        if (delay.TotalMilliseconds > ackdelay)
                        {
                            // unset cancelling order
                            o.Cancelling = false;

                            // Raise Ack Cancel
                            this.OrderPlugin.RaiseEventAckCancel(o.OrderID, o.Way, o.Quantity, o.Dispatcher);

                            // Remove Order
                            this.OrderCellsQueue.Remove(o);

                            // Order is out
                            this.OrderPlugin.RaiseEventOutReport(o.OrderID, o.Way, o.Dispatcher);
                        }
                    }
                    else if (o.Modifying)
                    {
                        var delay = Timings.Now - o.Stamp;
                        if (delay.TotalMilliseconds > ackdelay)
                        {
                            // unset modifying order
                            o.Modifying = false;

                            // check new quantity & price
                            if (o.NewQuantity <= 0)
                            {
                                this.OrderPlugin.RaiseEventRejModify(o.OrderID, o.Way, "Invalid New Quantity", o.Dispatcher);
                            }
                            else if (o.NewPrice <= 0.0)
                            {
                                this.OrderPlugin.RaiseEventRejModify(o.OrderID, o.Way, "Invalid New Price", o.Dispatcher);
                            }
                            else if (o.NewQuantity == o.Quantity && o.NewPrice == o.Price)
                            {
                                this.OrderPlugin.RaiseEventRejModify(
                                    o.OrderID, o.Way, "Invalid Price & Quantity : same requested price & quantity", o.Dispatcher);
                            }
                            else
                            {
                                o.Price = o.NewPrice;
                                o.Quantity = o.NewQuantity;
                                o.NewQuantity = 0;
                                o.NewPrice = 0.0;
                                this.OrderPlugin.RaiseEventAckModify(o.OrderID, o.Way, o.Quantity, o.Quantity, o.Price, o.Dispatcher);
                            }
                        }
                    }
                }
            }
        }

        #endregion
    }
}